Xenium Data Overview

In this vignette we’ll visualize the spatially-resolved, single cell RNA-seq profiles produced by the Xenium platform. The vignette demonstrates how to load the per-transcript location data, cell x gene matrix, cell segmentation, and cell centroid information available in the Xenium outputs. The resulting Seurat object will contain the gene expression profile of each cell, the centroid and boundary of each cell, and the location of each individual detected transcript. The per-cell gene expression profiles are similar to standard single-cell RNA-seq and can be analyzed using the same tools.

Setup

This vignette requires the latest version of Seurat on Github. First, you must install in-development versions of Seurat and SeuratObject that have Xenium support. It is recommended to update your package version at this step, as the required versions may not be fully captured in the dependencies yet.

library(remotes)
# remotes::install_github('satijalab/seurat')
library(Seurat)
library(SeuratObject)

Mouse Brain: Load Data

This vignette is based on the Tiny subset dataset from 10x Genomics provided in the Fresh Frozen Mouse Brain for Xenium Explorer Demo. These analysis steps are also compatible with the larger Full coronal section, but will take longer to execute.

First we read in the dataset and create a Seurat object. Provide the path to the data folder for a Xenium run as the input path. The RNA data is stored in the Xenium assay of the Seurat object. This step should take about a minute.

path <- "Xenium_V1_FF_Mouse_Brain_Coronal_Subset_CTX+HP/"

# Load the Xenium data
xenium.obj <- LoadXenium(path, fov = "fov")

# remove cells with 0 counts -- these cause problems for SCTransform
xenium.obj <- subset(xenium.obj, subset = nCount_Xenium > 0)

Spatial information is loaded into slots of the Seurat object, labelled by the name of “field of view” (FOV) being loaded. Initially all the data is loaded into the FOV named fov, by convention in this vignette. Later, we will make a cropped FOV that zooms into a region of interest. Here is a summary of the spatial information stored in the fov slot of the resulting Seurat object:

Cell Centroids: The spatial coordinates marking the centroid for each cell being profiled
# Get the center position of each centroid. There is one row per cell in this dataframe.
head(GetTissueCoordinates(xenium.obj[["fov"]][["centroids"]]))
x y cell
1898.815 2540.963 1
1895.305 2532.627 2
2368.073 2534.409 3
1903.726 2560.010 4
1917.481 2543.132 5
1926.540 2560.044 6
Cell Segmentation Boundaries: The spatial coordinates that describe the polygon segmentation of each single cell
# Get the coordinates for each segmentation vertex. Each cell will have a variable number of
# vertices describing its shape.
head(GetTissueCoordinates(xenium.obj[["fov"]][["segmentation"]]))
x y cell
1901.875 2526.413 1
1901.450 2537.038 1
1900.175 2539.375 1
1896.562 2539.800 1
1885.938 2537.887 1
1882.963 2542.775 1
Molecule positions: The spatial coordinates for each individual molecule that was detected by the assay
# Fetch molecules positions for Gad1
head(FetchData(xenium.obj[["fov"]][["molecules"]], vars = "Gad1"))
x y molecule
4854.597 756.7019 Gad1
4859.171 658.6622 Gad1
4867.069 864.6551 Gad1
4868.991 777.4815 Gad1
4873.433 885.4102 Gad1
4873.344 795.4069 Gad1

Standard QC plots

Standard QC plots provided by Seurat are available via the Xenium assay. Here are violin plots of genes per cell (nFeature_Xenium) and transcript counts per cell (nCount_Xenium)

VlnPlot(xenium.obj, features = c("nFeature_Xenium", "nCount_Xenium"), ncol = 2, pt.size = 0)

Check that the cell and molecule information is loaded correctly by plotting the transcript locations of a few marker genes over the cell centroids. ImageDimPlot() can display cell positions and overlay the location of individual molecules via the molecules argument. The nmols argument is used to downsample the number of transcript locations displayed to control overplotting.

ImageDimPlot(xenium.obj, fov = "fov", molecules = c("Cux2", "Foxp2"), nmols = 20000, mols.cols = c("blue", 
    "green"), axes = TRUE)

Here’s a similar plot, including the pan-inhibitory neuron marker Gad1, inhibitory neuron sub-type markers Pvalb, and Sst, and astrocyte marker Gfap.

ImageDimPlot(xenium.obj, fov = "fov", molecules = c("Gad1", "Sst", "Pvalb", "Gfap"), nmols = 20000)

Here we visualize the expression level of some key layer marker genes at the per-cell level using ImageFeaturePlot() which is analogous to the FeaturePlot() function for visualizing expression on a 2D embedding. We manually adjust the max.cutoff for each gene to roughly the 90th percentile of it’s count distribution to improve contrast.

ImageFeaturePlot(xenium.obj, features = c("Cux2", "Rorb", "Bcl11b", "Foxp2"), max.cutoff = c(25, 
    35, 12, 10), size = 0.75, cols = c("white", "red"))

Cropped Images and Cell Boundaries

We can zoom in on a chosen area with the Crop() function. Once zoomed-in, we can visualize cell segmentation boundaries along with individual molecules.

cropped.coords <- Crop(xenium.obj[["fov"]], x = c(1200, 2900), y = c(3750, 4550), coords = "plot")

xenium.obj[["zoom"]] <- cropped.coords

# visualize cropped area with cell segmentations & selected molecules
DefaultBoundary(xenium.obj[["zoom"]]) <- "segmentation"
ImageDimPlot(xenium.obj, fov = "zoom", axes = TRUE, border.color = "white", border.size = 0.1, cols = "polychrome", 
    coord.fixed = FALSE, molecules = c("Gad1", "Sst", "Npy2r", "Pvalb", "Nrn1"), nmols = 10000)

Preprocessing and unsupervised analysis

We start by performing a standard unsupervised clustering analysis, essentially first treating the dataset as a scRNA-seq experiment. We use SCTransform-based normalization followed by standard dimensionality reduction and clustering. This step takes about 5 minutes from start to finish.

xenium.obj <- SCTransform(xenium.obj, assay = "Xenium")
xenium.obj <- RunPCA(xenium.obj, npcs = 30, features = rownames(xenium.obj))
xenium.obj <- RunUMAP(xenium.obj, dims = 1:30)
xenium.obj <- FindNeighbors(xenium.obj, reduction = "pca", dims = 1:30)
xenium.obj <- FindClusters(xenium.obj, resolution = 0.3)

We can then visualize the results of the clustering by coloring each cell according to its cluster either in UMAP space with DimPlot() or overlaid on the image with ImageDimPlot().

UMAPPlot(xenium.obj)

We can visualize the expression level of the markers we looked at earlier on the UMAP coordinates.

FeaturePlot(xenium.obj, features = c("Cux2", "Bcl11b", "Foxp2", "Gad1", "Sst", "Gfap"))

We can now use ImageDimPlot() to color the cell positions colored by the cluster labels determined in the previous step.

ImageDimPlot(xenium.obj, cols = "polychrome", axes = TRUE, size = 0.75)

This vignette and the analysis methods for in-situ data are a work in progress. Please contact if you have questions or requests for additional examples. If you have any issues with the code working, please report the results of sessionInfo().

Session Info
sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-conda-linux-gnu (64-bit)
## Running under: Amazon Linux 2
## 
## Matrix products: default
## BLAS/LAPACK: /mnt/opt/R/R-4.0.3-conda/env/lib/libopenblasp-r0.3.12.so
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] sp_1.4-4           SeuratObject_4.1.1 Seurat_4.1.0.9007  remotes_2.2.0     
## 
## loaded via a namespace (and not attached):
##   [1] Rtsne_0.15            colorspace_2.0-3      deldir_1.0-6         
##   [4] ggridges_0.5.2        rstudioapi_0.11       spatstat.data_3.0-0  
##   [7] farver_2.0.3          leiden_0.3.3          listenv_0.8.0        
##  [10] bit64_4.0.5           ggrepel_0.8.2         fansi_1.0.3          
##  [13] R.methodsS3_1.8.1     codetools_0.2-16      splines_4.0.3        
##  [16] cachem_1.0.6          knitr_1.40            polyclip_1.10-4      
##  [19] jsonlite_1.8.4        ica_1.0-2             cluster_2.1.0        
##  [22] R.oo_1.24.0           png_0.1-7             rgeos_0.5-9          
##  [25] uwot_0.1.14           shiny_1.5.0           sctransform_0.3.4    
##  [28] spatstat.sparse_3.0-0 compiler_4.0.3        httr_1.4.2           
##  [31] assertthat_0.2.1      Matrix_1.5-1          fastmap_1.1.0        
##  [34] lazyeval_0.2.2        cli_3.4.1             later_1.3.0          
##  [37] formatR_1.7           htmltools_0.5.3       tools_4.0.3          
##  [40] igraph_1.2.6          gtable_0.3.1          glue_1.6.2           
##  [43] RANN_2.6.1            reshape2_1.4.4        dplyr_1.0.10         
##  [46] Rcpp_1.0.9            scattermore_0.8       jquerylib_0.1.4      
##  [49] vctrs_0.5.0           nlme_3.1-150          progressr_0.11.0     
##  [52] lmtest_0.9-38         spatstat.random_3.0-1 xfun_0.34            
##  [55] stringr_1.4.0         globals_0.16.2        mime_0.9             
##  [58] miniUI_0.1.1.1        lifecycle_1.0.3       irlba_2.3.3          
##  [61] goftest_1.2-2         future_1.19.1         MASS_7.3-53          
##  [64] zoo_1.8-8             scales_1.2.1          spatstat.core_2.4-4  
##  [67] promises_1.1.1        spatstat.utils_3.0-1  parallel_4.0.3       
##  [70] RColorBrewer_1.1-2    yaml_2.3.6            reticulate_1.18      
##  [73] pbapply_1.4-3         gridExtra_2.3         ggplot2_3.3.6        
##  [76] sass_0.4.2            rpart_4.1-15          stringi_1.7.8        
##  [79] highr_0.8             rlang_1.0.6           pkgconfig_2.0.3      
##  [82] matrixStats_0.57.0    evaluate_0.17         lattice_0.20-41      
##  [85] tensor_1.5            ROCR_1.0-11           purrr_0.3.5          
##  [88] labeling_0.4.2        patchwork_1.0.1.9000  htmlwidgets_1.5.2    
##  [91] bit_4.0.4             cowplot_1.1.0         tidyselect_1.2.0     
##  [94] RcppAnnoy_0.0.19      plyr_1.8.8            magrittr_2.0.3       
##  [97] R6_2.5.1              generics_0.1.3        DBI_1.1.0            
## [100] mgcv_1.8-33           pillar_1.8.1          fitdistrplus_1.1-1   
## [103] abind_1.4-5           survival_3.2-7        tibble_3.1.8         
## [106] future.apply_1.6.0    crayon_1.3.4          KernSmooth_2.23-17   
## [109] utf8_1.2.2            spatstat.geom_3.0-3   plotly_4.9.2.1       
## [112] rmarkdown_2.17        grid_4.0.3            data.table_1.14.2    
## [115] blob_1.2.1            digest_0.6.31         xtable_1.8-4         
## [118] tidyr_1.1.2           httpuv_1.5.4          R.utils_2.10.1       
## [121] munsell_0.5.0         viridisLite_0.3.0     bslib_0.4.0